TypeScript · 7086 bytes Raw Blame History
1 'use client';
2
3 import { useState, useEffect } from 'react';
4 import { useParams } from 'next/navigation';
5 import Link from 'next/link';
6 import Image from 'next/image';
7 import Header from '@/components/Header';
8 import DocumentIcon from '@/components/DocumentIcon';
9 import { getAwardDetail, AwardDetail, AwardRecipient } from '@/lib/api';
10
11 export default function AwardDetailPage() {
12 const params = useParams();
13 const awardId = Number(params.id);
14
15 const [award, setAward] = useState<AwardDetail | null>(null);
16 const [loading, setLoading] = useState(true);
17 const [error, setError] = useState<string | null>(null);
18
19 useEffect(() => {
20 async function fetchAward() {
21 try {
22 const data = await getAwardDetail(awardId);
23 setAward(data);
24 } catch (err) {
25 setError('Failed to load award details');
26 console.error(err);
27 } finally {
28 setLoading(false);
29 }
30 }
31 if (awardId) {
32 fetchAward();
33 }
34 }, [awardId]);
35
36 // Helper to get image path
37 const getImagePath = (filename: string) => {
38 const extensions = ['.jpg', '.png', '.jpeg', '.gif'];
39 for (const ext of extensions) {
40 if (filename.toLowerCase().endsWith(ext)) {
41 return `/${filename}`;
42 }
43 }
44 return `/${filename}.jpg`;
45 };
46
47 const breadcrumbs = [
48 { label: 'Home', href: '/' },
49 { label: 'Awards', href: '/awards' },
50 { label: award?.name || 'Loading...' }
51 ];
52
53 if (loading) {
54 return (
55 <div className="min-h-screen bg-vmi-cream">
56 <Header breadcrumbs={breadcrumbs} showAwards={false} />
57 <main className="max-w-6xl mx-auto px-4 py-12">
58 <p className="text-center text-gray-600">Loading award details...</p>
59 </main>
60 </div>
61 );
62 }
63
64 if (error || !award) {
65 return (
66 <div className="min-h-screen bg-vmi-cream">
67 <Header breadcrumbs={breadcrumbs} showAwards={false} />
68 <main className="max-w-6xl mx-auto px-4 py-12">
69 <p className="text-center text-red-600">{error || 'Award not found'}</p>
70 <div className="text-center mt-4">
71 <Link href="/awards" className="text-vmi-red hover:underline">
72 Back to Awards
73 </Link>
74 </div>
75 </main>
76 </div>
77 );
78 }
79
80 return (
81 <div className="min-h-screen bg-vmi-cream">
82 <Header breadcrumbs={breadcrumbs} showAwards={false} />
83
84 <main className="max-w-6xl mx-auto px-4 py-12">
85 {/* Award Header Section */}
86 <div className="bg-vmi-light-gold border-2 border-vmi-gold rounded-lg p-8 mb-8 shadow-xl">
87 <div className="flex flex-col md:flex-row items-center gap-8">
88 {/* Award Image */}
89 <div className="relative w-32 h-44 flex-shrink-0">
90 <Image
91 src={getImagePath(award.image_filename)}
92 alt={award.name}
93 fill
94 className="object-contain"
95 sizes="128px"
96 priority
97 />
98 </div>
99
100 {/* Award Info */}
101 <div className="text-center md:text-left">
102 <h1 className="text-3xl md:text-4xl font-black mb-4 text-vmi-red">
103 {award.name}
104 </h1>
105 <p className="text-gray-700 text-lg mb-4">
106 {award.short_description}
107 </p>
108 <p className="text-vmi-red font-bold text-xl">
109 {award.recipient_count} VMI {award.recipient_count === 1 ? 'Recipient' : 'Recipients'}
110 {award.total_awards_given > award.recipient_count && (
111 <span className="text-gray-600 font-normal text-base ml-2">
112 ({award.total_awards_given} total awards)
113 </span>
114 )}
115 </p>
116 </div>
117 </div>
118 </div>
119
120 {/* Long Description Section */}
121 <div className="bg-white border-2 border-gray-300 rounded-lg p-8 mb-8 shadow-xl">
122 <h2 className="text-2xl font-bold mb-4 text-vmi-red">About This Award</h2>
123 <div className="prose max-w-none text-gray-700 whitespace-pre-line">
124 {award.long_description}
125 </div>
126 </div>
127
128 {/* Recipients Section */}
129 <div className="bg-white border-2 border-gray-300 rounded-lg p-8 shadow-xl">
130 <h2 className="text-2xl font-bold mb-6 text-vmi-red">
131 VMI Alumni Recipients
132 </h2>
133
134 {award.recipients.length === 0 ? (
135 <p className="text-center text-gray-600">
136 No VMI recipients have been added yet.
137 </p>
138 ) : (
139 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
140 {award.recipients.map((recipient: AwardRecipient) => (
141 <Link
142 key={`${recipient.person_id}-${recipient.count}`}
143 href={`/memorial/person/${recipient.person_id}`}
144 className="block p-4 rounded-lg border-2 border-gray-200 hover:border-vmi-gold hover:bg-vmi-light-gold transition-all duration-200 group"
145 >
146 <div className="flex items-start justify-between">
147 <div className="flex-1">
148 <h3 className="font-bold text-gray-800 group-hover:text-vmi-red transition-colors">
149 {recipient.display_name}
150 {recipient.count > 1 && (
151 <span className="ml-2 text-sm text-vmi-gold font-normal">
152 (×{recipient.count})
153 </span>
154 )}
155 </h3>
156 {recipient.class_year && (
157 <p className="text-sm text-gray-600">
158 Class of {recipient.class_year}
159 {recipient.class_letter && ` (${recipient.class_letter})`}
160 </p>
161 )}
162 <p className="text-sm text-gray-500">{recipient.conflict_name}</p>
163 {recipient.date_awarded && (
164 <p className="text-xs text-gray-400 mt-1">
165 Awarded: {new Date(recipient.date_awarded).toLocaleDateString()}
166 </p>
167 )}
168 </div>
169 {recipient.pdf_key && (
170 <DocumentIcon className="ml-2 flex-shrink-0" />
171 )}
172 </div>
173 {recipient.citation && (
174 <p className="mt-2 text-sm text-gray-600 line-clamp-2 italic">
175 &ldquo;{recipient.citation}&rdquo;
176 </p>
177 )}
178 </Link>
179 ))}
180 </div>
181 )}
182 </div>
183
184 {/* Back Link */}
185 <div className="mt-8 text-center">
186 <Link href="/awards" className="text-vmi-red hover:underline font-semibold">
187 Back to All Awards
188 </Link>
189 </div>
190 </main>
191 </div>
192 );
193 }